package bigapps.myneighbor.dao.impl;

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;

import bigapps.myneighbor.dao.GenericDao;
import bigapps.myneighbor.domain.NYCEntity;

public abstract class GenericEntityManagerDao<T extends NYCEntity, ID extends Serializable>
implements GenericDao<T, ID> {
	private static final int MAX_DISPLAY = 25;
	private static final int DISTANCE_LIMIT = 200;
	private EntityManager em;
	private Class<T> persistenceClass;

	public EntityManager getEntityManager() {
		return em;
	}

	@PersistenceContext(type = PersistenceContextType.EXTENDED)
	public void setEntityManager(EntityManager em) {
		this.em = em;
	}

	public T findById(ID id, boolean lock) {
		return em.find(persistenceClass, id);
	}

	@Transactional
	// (propagation = Propagation.REQUIRES_NEW)
	public void save(T entity) {
		try {
			//			EntityTransaction txn = em.getTransaction();
			//			txn.begin();
			//			if (findEntity(entity) == null) {
			em.persist(entity);
			System.out.println("entity created: " + entity);
			//			} else {
			//				System.out.println("entity already exist: " + entity);
			//			}
			//			txn.commit();
			//			System.out.println("transaction succeded");
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("transaction failed");
		}
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void updateLatitudeLongitude(T entity, Integer status,
			Double latitude, Double longitude) {
		entity.setStatus(status);
		entity.setLatitude(latitude);
		entity.setLongitude(longitude);
		em.merge(entity);
		System.out.println("entity updated: " + entity);
	}

	@Override
	public List<T> findByClosestDistance(double latitude, double longitude) {
		List<T> listOfEntity = findAll();
		for (T pf : listOfEntity) {
			calculateDistance(pf, latitude, longitude);
		}
		Collections.sort(listOfEntity, new DistanceComparator());
		int i;
		for(i = 0; i < MAX_DISPLAY; i++)
		{
			T entity = listOfEntity.get(i);
			if(entity.getDistance() > DISTANCE_LIMIT)
			{
				i--;
				break;
			}
		}
		List<T> listOfDisplayableParkingInfo = listOfEntity.subList(0,
				i + 1);

		return listOfDisplayableParkingInfo;
	}

	private void calculateDistance(T entity, double latitude, double longitude) {
		if (entity.getLatitude() == null || entity.getLongitude() == null) {
			entity.setDistance(Float.MAX_VALUE);
			System.out
			.println("either latitude or longitude is null("
					+ entity.getLatitude() + ", "
					+ entity.getLongitude() + ")");
		} else {
			double earthRadius = 3958.75;
			double dLat = Math.toRadians(latitude - (entity.getLatitude()));
			double dLng = Math.toRadians(longitude - (entity.getLongitude()));
			double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
			+ Math.cos(Math.toRadians(entity.getLatitude()))
			* Math.cos(Math.toRadians(latitude)) * Math.sin(dLng / 2)
			* Math.sin(dLng / 2);
			double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
			double dist = earthRadius * c;
			entity.setDistance(dist);
			System.out.println("From - Latitude: " + latitude + ", Longitude: "
					+ longitude + " To - Latitude: " + (entity.getLatitude())
					+ ", Longitude: " + (entity.getLongitude()) + " Distance: "
					+ entity.getDistance());
		}
	}

	private class DistanceComparator implements Comparator {
		public int compare(Object obj1, Object obj2) {
			NYCEntity pf1 = (NYCEntity) obj1;
			NYCEntity pf2 = (NYCEntity) obj2;
			int result = (int) (Math
					.ceil(pf1.getDistance() - pf2.getDistance()));
			return result;
		}
	}
}
